//
//  Player.m
//  PezMonkey
//
//  Created by Spence DiNicolantonio on 12/28/09.
//  Copyright 2009 Necrosoft. All rights reserved.
//

#import "Player.h"

#define kPlayerGroundCollisionType 1
#define kPlayerPlayerCollisionType 2
#define kPlayerFlagCollisionType 3

#define kPlayerGroundAccel 1000
#define kPlayerAirAccel 300
#define kPlayerJumpForce 5000


@implementation Player

@synthesize staticBody;
@synthesize player;
@synthesize playerShape;
@synthesize groundNormal;
@synthesize applause;

- (NSString *)demoName {
	return @"Player";
}

- (void)start {
	// create space
	self.space = [PMSpace spaceWithGravity:PMVectMake(0, -1.5)];
	[space setSubSteps:3];
	[space setIterations:10];
	
	// initialize shared static body
	self.staticBody = [PMBody staticBody];
	
	// initialize ground normal
	self.groundNormal = PMVectZero;
	
	// create segments around the screen
	[self addSegments];
	
	// create springboards
	[self addSpringboardWithLength:70 
						 stiffness:600 
						   damping:10
						   atPoint:PMVectMake(-130, 50)];
	[self addSpringboardWithLength:60 
						 stiffness:600 
						   damping:10
						   atPoint:PMVectMake(-20, 20)];
	[self addSpringboardWithLength:50 
						 stiffness:600 
						   damping:10
						   atPoint:PMVectMake(80, -10)];
	
	
	// define player attributes
	PMFloat radius = 15.0;
	PMFloat mass = 10.0;
	PMFloat moment = PMMomentForCircle(mass, 0, radius, PMVectZero);
	
	// create player body
	self.player = [PMBody bodyWithMass:mass moment:moment];
	[player setPosition:PMVectMake(0, -220)];
	[player setTerminalAngularVelocity:25];
	[space addBody:player];
	
	// create player shape
	self.playerShape = [PMCircleShape circleShapeWithBody:player radius:radius];
	[playerShape setFriction:1];
	[playerShape setElasticity:0];
	[playerShape setCollisionType:kPlayerPlayerCollisionType];
	[space addShape:playerShape];
    
    // create goal flag
    [self addFlagAtPoint:PMVectMake(240, 190)];
	
	// add collision callback for collisions between player and ground
	[space registerCollisionCallbackTarget:self 
								  selector:@selector(playerGroundCollision:) 
								  forTypeA:kPlayerGroundCollisionType 
									 typeB:kPlayerPlayerCollisionType];
    
    // add collision callback for collision between player and flag
	[space registerCollisionCallbackTarget:self 
								  selector:@selector(goalReached) 
								  forTypeA:kPlayerFlagCollisionType 
									 typeB:kPlayerPlayerCollisionType];
    
    // initialize applause sound
    applause = [NSSound soundNamed:@"applause"];
    [applause play];
    [applause stop];
}

- (void)update:(NSTimeInterval)deltaT {
	static NSInteger jumpedLast = 0;
		
	// reset forces on player
	[player resetForces];
		
	// is player jumping?
	jumping = upArrow;
	
	// is player on groung?
	BOOL onGround = (groundNormal.y > 0.0);
	
	// should player move
	BOOL shouldMove = (leftArrow ^ rightArrow);
	
	
	// jump only if on ground and have released key since last jump
	if (jumping && onGround && !jumpedLast) {
		PMVect jumpForce = PMVectSLerp(groundNormal, PMVectMake(0, 1), 0.75);
		jumpForce = PMVectMult(jumpForce, kPlayerJumpForce);
		[player applyImpulse:jumpForce];
	}
	
	
	// move if on ground
	if (onGround && shouldMove) {
		PMFloat moveAccel = (leftArrow)? kPlayerGroundAccel : -kPlayerGroundAccel;
		PMFloat moveTorque = player.mass * moveAccel * playerShape.radius;
		[player applyTorque:moveTorque];
	}
	
	// apply in-air movement force
	else if (!onGround && shouldMove) {
		PMFloat airAccel = (leftArrow)? -kPlayerAirAccel : kPlayerAirAccel;
		PMVect airMoveForce = PMVectMake(player.mass * airAccel, 0);
		[player applyForce:airMoveForce];
	}
		
	// reset ground normal
	groundNormal = cpvzero;
	
	// update jumpedLast
	jumpedLast = jumping;
}

/**
 * Creates and adds segment shapes to demo space to make a boundary along the 
 * edges of the screen as well as some platforms for the player to walk on.
 */
- (void)addSegments {
	// make sure static body has been initialized
	if (!staticBody)
		self.staticBody = [PMBody staticBody];
	
	// create temp shape
	PMShape *shape;
	
	// define boundary segment width 
	PMFloat width = 4;
		
	// create other segments to play with
	shape = [PMSegmentShape segmentShapeWithBody:staticBody
										   start:PMVectMake(-220, -200)
											 end:PMVectMake(-220, 180)
										   width:width];
	[shape setElasticity:1.0];
	[shape setFriction:0.0];
	[shape setLayers:PMLayerMaskNotGrabbable];
	[shape setCollisionType:kPlayerGroundCollisionType];
	[space addStaticShape:shape];
	
	shape = [PMSegmentShape segmentShapeWithBody:staticBody
										   start:PMVectMake(0, -240)
											 end:PMVectMake(320, -200)
										   width:width];
	[shape setElasticity:1.0];
	[shape setFriction:1.0];
	[shape setLayers:PMLayerMaskNotGrabbable];
	[shape setCollisionType:kPlayerGroundCollisionType];
	[space addStaticShape:shape];
	
	shape = [PMSegmentShape segmentShapeWithBody:staticBody
										   start:PMVectMake(200, -240)
											 end:PMVectMake(320, -100)
										   width:width];
	[shape setElasticity:1.0];
	[shape setFriction:1.0];
	[shape setLayers:PMLayerMaskNotGrabbable];
	[shape setCollisionType:kPlayerGroundCollisionType];
	[space addStaticShape:shape];
	
	shape = [PMSegmentShape segmentShapeWithBody:staticBody
										   start:PMVectMake(-220, -80)
											 end:PMVectMake(200, -80)
										   width:width];
	[shape setElasticity:1.0];
	[shape setFriction:1.0];
	[shape setLayers:PMLayerMaskNotGrabbable];
	[shape setCollisionType:kPlayerGroundCollisionType];
	[space addStaticShape:shape];
	
	shape = [PMSegmentShape segmentShapeWithBody:staticBody
										   start:PMVectMake(0, 190)
											 end:PMVectMake(320, 190)
										   width:width];
	[shape setElasticity:1.0];
	[shape setFriction:1.0];
	[shape setLayers:PMLayerMaskNotGrabbable];
	[shape setCollisionType:kPlayerGroundCollisionType];
	[space addStaticShape:shape];
	
	// steps:
	for (int i=0; i<4; i++) {
		shape = [PMSegmentShape segmentShapeWithBody:staticBody
											   start:PMVectMake(-320, -240+(i*120))
												 end:PMVectMake(-270, -240+(i*120))
											   width:width];
		[shape setElasticity:1.0];
		[shape setFriction:1.0];
		[shape setLayers:PMLayerMaskNotGrabbable];
		[shape setCollisionType:kPlayerGroundCollisionType];
		[space addStaticShape:shape];
		
		shape = [PMSegmentShape segmentShapeWithBody:staticBody
											   start:PMVectMake(-220, -180+(i*120))
												 end:PMVectMake(-270, -180+(i*120))
											   width:width];
		[shape setElasticity:1.0];
		[shape setFriction:1.0];
		[shape setLayers:PMLayerMaskNotGrabbable];
		[shape setCollisionType:kPlayerGroundCollisionType];
		[space addStaticShape:shape];
	}
	
	// create left screen boundary
	shape = [PMSegmentShape segmentShapeWithBody:staticBody
										   start:PMVectMake(-320 - width/2, -240)
											 end:PMVectMake(-320 - width/2, 340)
										   width:width];
	[shape setElasticity:1.0];
	[shape setFriction:0.0];
	[shape setLayers:PMLayerMaskNotGrabbable];
	[shape setCollisionType:kPlayerGroundCollisionType];
	[space addStaticShape:shape];
	
	// create right screen boundary
	shape = [PMSegmentShape segmentShapeWithBody:staticBody
										   start:PMVectMake(320 + width/2, -240) 
											 end:PMVectMake(320 + width/2, 340)
										   width:width];
	[shape setElasticity:1.0];
	[shape setFriction:0.0];
	[shape setLayers:PMLayerMaskNotGrabbable];
	[shape setCollisionType:kPlayerGroundCollisionType];
	[space addStaticShape:shape];
	
	// create bottom screen boundary
	shape = [PMSegmentShape segmentShapeWithBody:staticBody
										   start:PMVectMake(-320, -240) 
											 end:PMVectMake(320, -240)
										   width:width];
	[shape setElasticity:1.0];
	[shape setFriction:1.0];
	[shape setLayers:PMLayerMaskNotGrabbable];
	[shape setCollisionType:kPlayerGroundCollisionType];
	[space addStaticShape:shape];
}

- (void)addSpringboardWithLength:(PMFloat)sbLength stiffness:(PMFloat)stiffness damping:(PMFloat)damping atPoint:(PMVect)point {
	// define springboard attributes
	PMFloat sbWidth = 10;
	PMFloat sbMass = 4;
	PMVect start = PMVectMake(-sbLength/2, 0);
	PMVect end = PMVectMake(sbLength/2, 0);
	
	// define springboard vertices
	PMFloat sbMoment = PMInfinity;//PMMomentForPolygon(sbMass, sbVerts, sbVertCount, PMVectZero);
	
	// create springboard body
	PMBody *springboard = [PMBody bodyWithMass:sbMass moment:sbMoment];
	[springboard setPosition:point];
	[springboard setTerminalAngularVelocity:0];
	[space addBody:springboard];
	
	// create springboard shape
	PMShape *shape = [PMSegmentShape segmentShapeWithBody:springboard 
													start:start 
													  end:end 
													width:sbWidth];
	[shape setFriction:1];
	[shape setElasticity:0];
	[shape setCollisionType:kPlayerGroundCollisionType];
	[space addShape:shape];
	
	
	// add springs to springboard
	PMFloat springLength = sbLength / 20;
	PMDampedSpring *spring;
	
	// left
	spring = [PMDampedSpring dampedSpringWithAnchor:start 
											onBodyA:springboard
											 anchor:PMVectMake(point.x - sbLength/2 - sbLength/2, point.y)
											onBodyB:staticBody 
										 restLength:springLength 
										  stiffness:stiffness 
											damping:damping];
	[space addConstraint:spring];
	
	// right
	spring = [PMDampedSpring dampedSpringWithAnchor:end
											onBodyA:springboard 
											 anchor:PMVectMake(point.x + sbLength/2 + sbLength/2, point.y)
											onBodyB:staticBody 
										 restLength:springLength 
										  stiffness:stiffness 
											damping:damping];
	[space addConstraint:spring];
}

- (void)addFlagAtPoint:(PMVect)point {
    // define flag attributes
    PMFloat height = 40;
    PMFloat length = 20;
    PMFloat width = 10;
    
    PMVect flagVerts[] = {
        PMVectMake(point.x, point.y + height),
        PMVectMake(point.x + length, point.y + height - width/2),
        PMVectMake(point.x, point.y + height - width),
    };
    
    PMShape *shape;
    
    // create flag pole
    shape = [PMSegmentShape segmentShapeWithBody:staticBody 
                                           start:point 
                                             end:PMVectMake(point.x, point.y + height)];
    [shape setSensor:YES];
    [shape setCollisionType:kPlayerFlagCollisionType];
    [space addStaticShape:shape];
    
    // create flag
    
    shape = [PMPolygonShape polygonShapeWithBody:staticBody 
                                        vertices:flagVerts 
                                       vertCount:3];
    [shape setSensor:YES];
    [space addStaticShape:shape];
             
}

#pragma mark -
#pragma mark Collision Callbacks

- (BOOL)playerGroundCollision:(PMCollision *)collision {
	// determine shape order
	PMShape *ground;
	PMVect normal;
	BOOL playerIsA;
	if ([playerShape isEqual:collision.shapeA]) { 
		playerIsA = YES;
		ground = collision.shapeB;
	} else {
		playerIsA = NO;
		ground = collision.shapeB;
	}
	
	// update ground normal
	if (collision.state == PMCollisionStatePreSolve) {
		if (playerIsA)
			normal = PMVectInverse([collision normalOfContact:0]);
		else
			normal = [collision normalOfContact:0];
		
		// replace current ground normal?
		if (normal.y > groundNormal.y)
			groundNormal = normal;
	}		 
	
	return YES;
}

- (void)goalReached {
    // play sound if not already playing
    if (![applause isPlaying])
        [applause play];
}


#pragma mark -
#pragma mark Dealloc

- (void)dealloc {
	[staticBody release];
	[player release];
	[playerShape release];
    [applause release];
	[super dealloc];
}

@end
